import 'dart:async';
import 'dart:io';

import 'package:biometrics/components/sendable_rect.dart';
import 'package:biometrics/components/task_helper.dart';
import 'package:flutter/services.dart';
import 'package:opencv_dart/opencv_dart.dart' as cv;

class Detector {
  factory Detector() => _instance;
  static final Detector _instance = Detector._internal();
  Detector._internal() {
    init();
  }

  static (int, int) modalSize = (320, 320);

  Completer<cv.FaceDetectorYN> _completer = Completer();
  final TaskHelper _taskHelper = TaskHelper("detect");

  Future<cv.FaceDetectorYN> init() async {
    if (_completer.isCompleted) return await _completer.future;
    final tmpModelPath = await copyAssetFileToTmp(
        "assets/models/face_detection_yunet_2023mar.onnx");

    final modalFile = File(tmpModelPath);
    final buf = await modalFile.readAsBytes();
    final detector = cv.FaceDetectorYN.fromBuffer(
      "onnx",
      buf,
      Uint8List(0),
      (320, 320),
    );
    _completer.complete(detector);
    return detector;
  }

  /// 销毁资源
  void dispose() async {
    if (_completer.isCompleted) {
      final detector = await _completer.future;
      detector.dispose();
    }
    _completer = Completer();
  }

  /// 检测人脸
  Future<(List<SendableRect>, cv.Mat)?> detect(cv.Mat data) async {
    late cv.Mat? faces;
    late int width, height;
    final ret = await _taskHelper.addTaskWhileIdle<(cv.Mat, cv.Mat)>(
        () => _detectFacesMatFromImageMat(data));
    if (ret == null) return null;
    faces = ret.$1;
    width = data.cols;
    height = data.rows;

    final list = await _detect(faces, width, height);
    return (list, ret.$2);
  }

  Future<(cv.Mat, cv.Mat)> _detectFacesMatFromImageMat(cv.Mat img) async {
    final detector = await _completer.future;
    img = cv.resize(img, modalSize);
    final ret = (detector.detect(img), img);
    return ret;
  }

  Future<List<SendableRect>> _detect(
    cv.Mat faces,
    int width,
    int height,
  ) async {
    final xRatio = width / modalSize.$1;
    final yRatio = height / modalSize.$2;

    final returnValue = List.generate(faces.rows, (i) {
      final x = (faces.at<double>(i, 0) * xRatio).toInt();
      final y = (faces.at<double>(i, 1) * yRatio).toInt();
      final width = (faces.at<double>(i, 2) * xRatio).toInt();
      final height = (faces.at<double>(i, 3) * yRatio).toInt();
      final rightEye = (
        (faces.at<double>(i, 4) * xRatio).toInt(),
        (faces.at<double>(i, 5) * yRatio).toInt()
      );
      final leftEye = (
        (faces.at<double>(i, 6) * xRatio).toInt(),
        (faces.at<double>(i, 7) * yRatio).toInt()
      );
      final nose = (
        (faces.at<double>(i, 8) * xRatio).toInt(),
        (faces.at<double>(i, 9) * yRatio).toInt()
      );
      final rcMouth = (
        (faces.at<double>(i, 10) * xRatio).toInt(),
        (faces.at<double>(i, 11) * yRatio).toInt()
      );
      final lcMouth = (
        (faces.at<double>(i, 12) * xRatio).toInt(),
        (faces.at<double>(i, 13) * yRatio).toInt()
      );
      final correctedWidth = (x + width) > width ? width - x : width;
      final correctedHeight = (y + height) > height ? height - y : height;
      final rawDetection =
          List.generate(faces.width, (index) => faces.at<double>(i, index));

      return SendableRect(
        x: x,
        y: y,
        width: width,
        height: height,
        rawDetection: rawDetection,
        rightEye: rightEye,
        leftEye: leftEye,
        nose: nose,
        rcMouth: rcMouth,
        lcMouth: lcMouth,
      );
    });

    return returnValue;
  }
}
